/* machine_pwm.c */

#include <stdio.h>

#include "machine_pwm.h"
#include "hal_tim.h"
#include "hal_gpio.h"
#include "board_init.h"

/*
 * Declerations.
 */

/* Local functions. */
           void machine_pwm_enable_clock(uint32_t tim_id, bool enable);
STATIC mp_obj_t machine_pwm_obj_init_helper(const machine_pwm_obj_t *self, size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args);

/* Class method, which would be called by class name. */
STATIC     void machine_pwm_obj_print(const mp_print_t *print, mp_obj_t o, mp_print_kind_t kind);
//STATIC mp_obj_t machine_pwm_obj_call(mp_obj_t self_in, mp_uint_t n_args, mp_uint_t n_kw, const mp_obj_t *args);
       mp_obj_t machine_pwm_obj_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args);


/* Instance methods, which would be called by class instance name. */
STATIC mp_obj_t machine_pwm_init(size_t n_args, const mp_obj_t *args, mp_map_t *kw_args);

/*
 * Functions.
 */

/* init(). */
STATIC mp_obj_t machine_pwm_init(size_t n_args, const mp_obj_t *args, mp_map_t *kw_args)
{
    /* args[0] is machine_pwm_obj_t. */
    //printf("machine_pwm_init().\r\n");
    return machine_pwm_obj_init_helper(args[0], n_args - 1, args + 1, kw_args);
}
MP_DEFINE_CONST_FUN_OBJ_KW(machine_pwm_init_obj, 1, machine_pwm_init);

/* deinit(). */
STATIC mp_obj_t machine_pwm_deinit(mp_obj_t self_in)
{
    machine_pwm_obj_t *self = MP_OBJ_TO_PTR(self_in);

    // Valid channel?
    if (self->tim_ch < MACHIEN_PWM_CH_NUM_PER_TIM)
    {
        // Mark it unused, and tell the hardware to stop routing
        self->port_conf->active_channels &= ~(1u << (self->tim_ch) );

        /* disable the pwm output. */
        TIM_OutputCompareConf_Type tim_out_conf;
        tim_out_conf.ChannelValue = 0u; /* disable output pwm. */
        tim_out_conf.EnableFastOutput = false;
        tim_out_conf.EnablePreLoadChannelValue = false; /* disable preload, load channel value immediately. */
        tim_out_conf.RefOutMode = TIM_OutputCompareRefOut_FallingEdgeOnMatch;
        tim_out_conf.ClearRefOutOnExtTrigger = false;
        tim_out_conf.PinPolarity = TIM_PinPolarity_Rising;
        TIM_EnableOutputCompare(self->tim_port, self->tim_ch, &tim_out_conf);


        /* stop the timer when necessary. */
        if ( 0u == (self->port_conf->active_channels) )
        {
            TIM_Stop(self->tim_port);
            machine_pwm_enable_clock(self->pwm_id / MACHIEN_PWM_CH_NUM_PER_TIM, false);
        }
    }
    return mp_const_none;
}
STATIC MP_DEFINE_CONST_FUN_OBJ_1(machine_pwm_deinit_obj, machine_pwm_deinit);

/* parameter list. */
typedef enum
{
    PWM_INIT_ARG_freq = 0,
    PWM_INIT_ARG_duty,
    //PWM_INIT_ARG_init,
} machine_pwm_init_arg_t;

STATIC mp_obj_t machine_pwm_obj_init_helper (
    const machine_pwm_obj_t *self, /* machine_pwm_obj_t类型的变量，包含硬件信息 */
    size_t n_args, /* 位置参数数量 */
    const mp_obj_t *pos_args, /* 位置参数清单 */
    mp_map_t *kw_args ) /* 关键字参数清单结构体 */
{
    //printf("%machine_pwm_obj_init_helper().\r\n");
    static const mp_arg_t allowed_args[] =
    {
        [PWM_INIT_ARG_freq] { MP_QSTR_freq , MP_ARG_REQUIRED | MP_ARG_INT,  {.u_int = 0} },
        [PWM_INIT_ARG_duty] { MP_QSTR_duty , MP_ARG_KW_ONLY  | MP_ARG_INT,  {.u_int = 0} },
        //[PWM_INIT_ARG_init] { MP_QSTR_init , MP_ARG_KW_ONLY  | MP_ARG_BOOL, {.u_bool = false} },
    };

    /* 解析参数 */
    mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
    mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);


    int freq = args[PWM_INIT_ARG_freq].u_int;

    //printf("freq = %d\r\n", freq);
    //printf("active_channels = %ld\r\n", self->port_conf->active_channels);

    if (freq < 0)
    {
        mp_raise_ValueError(MP_ERROR_TEXT("freq value is not available"));
    }
    int duty = args[PWM_INIT_ARG_duty].u_int;
    if ( (duty < 0) || (duty > MACHINE_PWM_DUTY_NUM) )
    {
        mp_raise_ValueError(MP_ERROR_TEXT("duty value is not available"));
    }

    /* setup pinmux. */
    GPIO_Init_Type gpio_init;
    gpio_init.Speed = GPIO_Speed_50MHz;
    gpio_init.Pins = ( 1u << (self->pwm_pin_obj->gpio_pin) );
    gpio_init.PinMode = GPIO_PinMode_AF_PushPull;
    GPIO_Init(self->pwm_pin_obj->gpio_port, &gpio_init);
    GPIO_PinAFConf(self->pwm_pin_obj->gpio_port, gpio_init.Pins, self->pwm_pin_af);

    /* Init the timer when necessary. */
    if ( (self->port_conf->active_channels | (1u << self->tim_ch)) == (1u << self->tim_ch) )  /* 仅当前通道激活或者无任何通道激活 */
    {
        /* enable clock for tim. */
        machine_pwm_enable_clock(self->pwm_id / MACHIEN_PWM_CH_NUM_PER_TIM, true);

        TIM_Init_Type tim_init;
        tim_init.ClockFreqHz = CLOCK_SYS_FREQ;
        tim_init.StepFreqHz = MACHINE_PWM_DUTY_NUM * freq;
        tim_init.Period = MACHINE_PWM_DUTY_NUM-1;
        tim_init.EnablePreloadPeriod = true;
        tim_init.PeriodMode = TIM_PeriodMode_Continuous;
        TIM_Init(self->tim_port, &tim_init);
        TIM_EnableOutputCompareSwitch(self->tim_port, true); /* unlock the output. */
        TIM_Start(self->tim_port);
        self->port_conf->freq = freq;
        //printf("set freq.\r\n");
    }

    /* add channel. */
    TIM_OutputCompareConf_Type tim_out_conf;
    tim_out_conf.ChannelValue = duty;
    tim_out_conf.EnableFastOutput = true;
    tim_out_conf.EnablePreLoadChannelValue = true;
    tim_out_conf.RefOutMode = TIM_OutputCompareRefOut_FallingEdgeOnMatch;
    tim_out_conf.ClearRefOutOnExtTrigger = false;
    tim_out_conf.PinPolarity = TIM_PinPolarity_Rising;
    TIM_EnableOutputCompare(self->tim_port, self->tim_ch, &tim_out_conf);

    self->ch_conf->duty = duty;
    self->port_conf->active_channels |= (1u << (self->tim_ch));

    return mp_const_none;
}

/* duty(). */
STATIC mp_obj_t machine_pwm_duty(size_t n_args, const mp_obj_t *args)
{
    machine_pwm_obj_t *self = MP_OBJ_TO_PTR(args[0]);

    if (n_args == 1) /* get */
    {
        return MP_OBJ_NEW_SMALL_INT(self->ch_conf->duty);
    }

    /* set */
    int duty = mp_obj_get_int(args[1]);
    if ((duty < 0) || (duty > MACHINE_PWM_DUTY_NUM) )
    {
        mp_raise_ValueError(MP_ERROR_TEXT("freq value is not available"));
    }
    TIM_PutChannelValue(self->tim_port, self->tim_ch, duty);
    self->ch_conf->duty = duty;

    return mp_const_none;
}
STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(machine_pwm_duty_obj, 1, 2, machine_pwm_duty);


/* freq(). */
STATIC mp_obj_t machine_pwm_freq(size_t n_args, const mp_obj_t *args)
{
    machine_pwm_obj_t *self = MP_OBJ_TO_PTR(args[0]);

    if (n_args == 1) /* get */
    {
        return MP_OBJ_NEW_SMALL_INT(self->port_conf->freq);
    }

    /* set */
    int freq = mp_obj_get_int(args[1]);
    if (freq < 0)
    {
        mp_raise_ValueError(MP_ERROR_TEXT("freq value is not available"));
    }

    if (  (self->port_conf->active_channels | (1u << (self->tim_ch)) ) != (1u << (self->tim_ch)) )
    {
        mp_raise_ValueError(MP_ERROR_TEXT("freq can not be changed when the current timer is sharing."));
    }

    TIM_Stop(self->tim_port);
    TIM_Init_Type tim_init;
    tim_init.ClockFreqHz = CLOCK_SYS_FREQ;
    tim_init.StepFreqHz = MACHINE_PWM_DUTY_NUM * freq;
    tim_init.Period = MACHINE_PWM_DUTY_NUM;
    tim_init.EnablePreloadPeriod = false;
    tim_init.PeriodMode = TIM_PeriodMode_Continuous;
    TIM_Init(self->tim_port, &tim_init);
    TIM_Start(self->tim_port);
    self->port_conf->freq = freq;

    return mp_const_none;
}
STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(machine_pwm_freq_obj, 1, 2, machine_pwm_freq);


STATIC     void machine_pwm_obj_print(const mp_print_t *print, mp_obj_t o, mp_print_kind_t kind)
{
    /* o is the machine_pwm_obj_t. */
    (void)kind;
    const machine_pwm_obj_t *self = MP_OBJ_TO_PTR(o);

    if ( 0u != (self->port_conf->active_channels & (1u << (self->tim_ch))) )
    {
        mp_printf(print, "PMW(%d, freq=%d, duty=%d), on Pin(%s)",
            self->pwm_id, self->port_conf->freq, self->ch_conf->duty,
            qstr_str(self->pwm_pin_obj->name));
    }
    else
    {
        mp_printf(print, "PMW(%d), on Pin(%s)", self->pwm_id, qstr_str(self->pwm_pin_obj->name));
    }
}

#if 0
STATIC mp_obj_t machine_pwm_obj_call(mp_obj_t self_in, mp_uint_t n_args, mp_uint_t n_kw, const mp_obj_t *args)
{
    /* self_in is machine_pwm_obj_t. */
    mp_arg_check_num(n_args, n_kw, 0, 1, false);
    //machine_pwm_obj_t *self = self_in;

    if ( n_args == 0 )  /* read value. */
    {
        return machine_pwm_read_u16(self_in);
    }
    else /* write value. */
    {
        return mp_const_none;
    }
}
#endif

/* return an instance of machine_pwm_obj_t. */
mp_obj_t machine_pwm_obj_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args)
{
    mp_arg_check_num(n_args, n_kw, 1, MP_OBJ_FUN_ARGS_MAX, true);

    const machine_pwm_obj_t *pwm = pwm_find(args[0]);

    if ( (n_args >= 1) || (n_kw >= 0) )
    {
        mp_map_t kw_args;
        mp_map_init_fixed_table(&kw_args, n_kw, args + n_args); /* 将关键字参数从总的参数列表中提取出来，单独封装成kw_args。 */
        machine_pwm_obj_init_helper(pwm, n_args - 1, args + 1, &kw_args);
    }

    return (mp_obj_t)pwm;
}


/* class locals_dict_table. */
STATIC const mp_rom_map_elem_t machine_pwm_locals_dict_table[] =
{
    /* Class instance methods. */
    { MP_ROM_QSTR(MP_QSTR_init),   MP_ROM_PTR(&machine_pwm_init_obj  ) },
    { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&machine_pwm_deinit_obj) },
    { MP_ROM_QSTR(MP_QSTR_freq),   MP_ROM_PTR(&machine_pwm_freq_obj  ) },
    { MP_ROM_QSTR(MP_QSTR_duty),   MP_ROM_PTR(&machine_pwm_duty_obj  ) },

};
STATIC MP_DEFINE_CONST_DICT(machine_pwm_locals_dict, machine_pwm_locals_dict_table);


const mp_obj_type_t machine_pwm_type =
{
    { &mp_type_type },
    .name        = MP_QSTR_PWM,
    .print       = machine_pwm_obj_print, /* __repr__(), which would be called by print(<ClassName>). */
    //.call        = machine_pwm_obj_call,  /* __call__(), which can be called as <ClassName>(). */
    .make_new    = machine_pwm_obj_make_new, /* create new class instance. */
    .locals_dict = (mp_obj_dict_t *)&machine_pwm_locals_dict,
};

/*
 * User functions.
 */
/* 格式化PMW对象，传入参数无论是已经初始化好的PWM对象，还是一个表示PWM清单中的索引编号，通过本函数都返回一个期望的pwm对象。 */
const machine_pwm_obj_t *pwm_find(mp_obj_t user_obj)
{
    //const machine_pin_obj_t *pin_obj;
    /* 如果传入参数本身就是一个PWM的实例，则直接送出这个PWM。 */
    if ( mp_obj_is_type(user_obj, &machine_pwm_type) )
    {
        return user_obj;
    }

    /* 如果传入参数本身就是一个Pin的实例，则通过倒排查询找到包含这个Pin对象的PWM通道。 */
    if ( mp_obj_is_type(user_obj, &machine_pin_type) )
    {
        for (uint32_t i = 0u; i < MACHIEN_PWM_CH_NUM_ALL; i++)
        {
            machine_pin_obj_t * pin_obj = (machine_pin_obj_t *)(user_obj);
            if (   (pin_obj->gpio_port == machine_pwm_objs[i]->pwm_pin_obj->gpio_port)
                && (pin_obj->gpio_pin  == machine_pwm_objs[i]->pwm_pin_obj->gpio_pin)  )
            {
                return machine_pwm_objs[i];
            }
        }
    }

    /* 如果传入参数是一个PWM通道号，则通过索引在PWM清单中找到这个通道，然后送出这个通道。 */
    if ( mp_obj_is_small_int(user_obj) )
    {
        uint8_t pwm_idx = MP_OBJ_SMALL_INT_VALUE(user_obj);
        if ( pwm_idx < MACHIEN_PWM_CH_NUM_ALL )
        {
            return machine_pwm_objs[pwm_idx];
        }
    }

    mp_raise_ValueError(MP_ERROR_TEXT("PWM doesn't exist"));
}

void machine_pwm_enable_clock(uint32_t tim_id, bool enable)
{
    switch (tim_id)
    {
        case 0u:
            RCC_EnableAPB1Periphs(RCC_APB1_PERIPH_TIM3, enable);
            break;
        case 1u:
            RCC_EnableAPB1Periphs(RCC_APB1_PERIPH_TIM4, enable);
            break;
        case 2u:
            RCC_EnableAPB1Periphs(RCC_APB1_PERIPH_TIM2, enable);
            break;
        case 3u:
            RCC_EnableAPB1Periphs(RCC_APB1_PERIPH_TIM5, enable);
            break;
        case 4u:
            RCC_EnableAPB2Periphs(RCC_APB2_PERIPH_TIM1, enable);
            break;
        case 5u:
            RCC_EnableAPB2Periphs(RCC_APB2_PERIPH_TIM8, enable);
            break;
        default:
            break;
    }
}

/* EOF. */

